﻿/****************************************************************
  Author:  Ievgenii Nazaruk, ievgenii.nazaruk@gmail.com 
 License:  Microsoft Public License (Ms-PL)
          ( http://www.opensource.org/licenses/ms-pl.html or
            http://www.microsoft.com/opensource/licenses.mspx )
*****************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TableParsers;
using System.Runtime.Serialization;
using System.ComponentModel;

namespace Speakup
{
    public enum ClassStatus
    {
        Attended,
        Missed,
        Cancelled,
        Reserved,
        ReservedWaitList,
        Available,
        AvailableWaitList,
        Unknown
    }    

    public class ClassStatusChangedArgs: EventArgs
    {
        public ClassStatus OldStatus {get; set;}
        public ClassStatus NewStatus {get; set;}
        public Class Source {get; set;}
    }
    
    [Serializable]
    public class Class : INotifyPropertyChanged
    {
        /// <summary>
        /// Class id - an internal id number of class that is used by speakup server.
        /// </summary>
        private int m_classId = 0;
        public int ClassId
        {
            get 
            { 
                return m_classId; 
            }
            internal set 
            {
                m_classId = value;        
            }
        }

        /// <summary>
        /// Class name
        /// </summary>
        private ClassName m_className = null;
        public ClassName ClassName
        {
            get 
            { 
                return m_className; 
            }
            internal set 
            { 
                m_className = value;            
            }
        }

        /// <summary>
        /// Class start/end time
        /// </summary>
        private ClassTime m_classTime = null;
        public ClassTime ClassTime
        {
            get 
            { 
                return m_classTime;
            }
            internal set 
            { 
                m_classTime = value;                
            }
        }

        /// <summary>
        /// Class status.
        /// </summary>
        private ClassStatus m_classStatus = ClassStatus.Unknown;
        public ClassStatus ClassStatus
        {
            get 
            { 
                return m_classStatus;  
            }
            internal set 
            {
                if (value != m_classStatus)
                {
                    ClassStatus oldStatus = m_classStatus;
                    m_classStatus = value;

                    OnClassStatusChanged(oldStatus, value);
                }
            }
        }

        private String m_theme = null;
        public String Theme
        {
            get
            {
                return m_theme;
            }
            internal set
            {
                m_theme = value;
            }
        }

        private bool m_themeAttended = false;
        public bool ThemeAttended
        {
            get
            {
                return m_themeAttended;
            }
            internal set
            {
                m_themeAttended = value;
            }
        }

        public override bool Equals(object obj)
        {
            Class speakupClass = obj as Class;
            if (speakupClass == null)
            {
                throw new InvalidCastException();
            }

            if (speakupClass.ClassId != ClassId)
            {
                return false;
            }

            if (speakupClass.ClassTime.Begin != ClassTime.Begin)
            {
                return false;
            }

            if (speakupClass.ClassTime.End != ClassTime.End)
            {
                return false;
            }

            if (speakupClass.ClassTime.Date != ClassTime.Date)
            {
                return false;
            }

            if (speakupClass.ClassName.Kind != ClassName.Kind)
            {
                return false;
            }

            if (speakupClass.ClassName.Name != ClassName.Name)
            {
                return false;
            }

            return true;
        }

        public override int GetHashCode()
        {
            int hash = ClassId;
            unchecked
            {
                hash += 83 * ClassTime.Begin.GetHashCode();
                hash += 83 * ClassTime.End.GetHashCode();
                hash += 83 * ClassTime.Date.GetHashCode();
                hash += 83 * ClassName.Kind.GetHashCode();
                hash += 83 * ClassName.Name.GetHashCode();
            }
            return hash;
        }

        private ClassStatus? m_originalStatus = null;
        /// <summary>
        /// Returns true if status of class of modifie
        /// </summary>
        public bool IsStatusModified
        {
            get
            {
                if(m_originalStatus == null)
                {
                    return false;
                }

                return m_originalStatus != m_classStatus;
            }
        }

        public void RestoreStatus()
        {
            if (IsStatusModified)
            {
                if (m_originalStatus == null) throw new InvalidProgramException("original state is null");

                //?? 0 - to bypass compilation warning of incompatible types.
                //m_originalStatus is guranteed to be non-null in here.                
                ClassStatus = m_originalStatus ?? 0;
            }
        }

        /// <summary>
        /// Set new status and sets "modified" flag.
        /// </summary>
        /// <param name="newStatus"></param>
        internal void ModifyStatus(ClassStatus newStatus)
        {
            if (m_originalStatus == null)
            {
                m_originalStatus = ClassStatus;
            }
            ClassStatus = newStatus;
        }

        /// <summary>
        /// Is class available?
        /// (class status is Available or AvailableWaitList)
        /// </summary>
        public bool IsAvailable
        {
            get
            {
                return ClassStatus == ClassStatus.Available ||
                       ClassStatus == ClassStatus.AvailableWaitList;
            }
        }

        /// <summary>
        /// Is class reserved?
        /// (Class status Reserved or ReservedWaitList)
        /// </summary>
        public bool IsReserved
        {
            get
            {
                return ClassStatus == ClassStatus.Reserved ||
                        ClassStatus == ClassStatus.ReservedWaitList;
            }
        }

        /// <summary>
        /// Can this class be reserved?
        /// (Class status Available or AvailableWaitList)
        /// </summary>
        public bool IsReservable
        {
            get
            {
                return IsAvailable;        
            }
        }
        
        /// <summary>
        /// Is class cancellable?
        /// (Class status Resreved or ReservedWaitList)
        /// </summary>
        public bool IsCancelable
        {
            get
            {
                return IsReserved;
            }
        }

        /// <summary>
        /// Tries to reserve class if class is available for reservation.
        /// If class already reserved this function does nothing.
        /// If reservation is invalid for current class status (f.e. class is alread been attended)
        /// then an ArgumentException is thrown.
        /// </summary>
        public void Reserve()
        {
            if (IsReservable)
            {
                if (m_originalStatus == null)
                {
                    m_originalStatus = ClassStatus;
                }

                if (ClassStatus == ClassStatus.Available)
                {
                    ClassStatus = ClassStatus.Reserved;
                }
                else
                {
                    ClassStatus = ClassStatus.ReservedWaitList;
                }
            }
            else if (!IsReserved)
            {
                throw new ArgumentException("Can not reserve class that is has "+ClassStatus+" status");
            }            
        }

        /// <summary>
        /// Tries to cancel reservation of reserved class.
        /// If class is available this function does nothing.
        /// If this operation is invalid for current class status (f.e. class is alread been attended)
        /// then an ArgumentException is thrown.
        /// </summary>
        public void CancelReservation()
        {
            if (IsCancelable)
            {
                if (m_originalStatus == null)
                {
                    m_originalStatus = ClassStatus;
                }

                if (ClassStatus == ClassStatus.Reserved)
                {
                    ClassStatus = ClassStatus.Available;
                }
                else
                {
                    ClassStatus = ClassStatus.ReservedWaitList;
                }                
            }
            else if (!IsAvailable)
            {
                throw new ArgumentException("Can not cancel reservation on class that has " + ClassStatus + " status");
            }

        }

        /// <summary>
        /// Event is fired whenever class status is changed.
        /// </summary>
        
        [field:NonSerialized]
        public event EventHandler<ClassStatusChangedArgs> ClassStatusChanged;

        protected virtual void OnClassStatusChanged(ClassStatus oldStatus, ClassStatus newStatus)
        {
            if (ClassStatusChanged != null)
            {
                ClassStatusChangedArgs args = new ClassStatusChangedArgs();
                args.Source = this;
                args.OldStatus = oldStatus;
                args.NewStatus = newStatus;

                ClassStatusChanged(this, args);
            }

            OnPropertyChanged("ClassStatus");
        }


        [field: NonSerialized]
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventArgs args = new PropertyChangedEventArgs(propertyName);
            if (PropertyChanged != null)
            {
                PropertyChanged(this, args);
            }
        }
    }
}
